#pragma once

#include <string>
#include <cstdint>
#include <istream>
#include <ostream>
#include <list>
#include <string>

/// Token type
enum class TokenType : std::int32_t {
    NONE = 0,
    IDENTIFIER,
    KEYWORD_STRUCT,
    KEYWORD_SERVICE,
    KEYWORD_STRING,
    KEYWORD_VOID,
    KEYWORD_I8,
    KEYWORD_I16,
    KEYWORD_I32,
    KEYWORD_I64,
    KEYWORD_UI8,
    KEYWORD_UI16,
    KEYWORD_UI32,
    KEYWORD_UI64,
    KEYWORD_FLOAT,
    KEYWORD_DOUBLE,
    KEYWORD_SEQ,
    KEYWORD_SET,
    KEYWORD_DICT,
    KEYWORD_BOOL,
    KEYWORD_IMPORT,
    KEYWORD_ONEWAY,
    KEYWORD_MULTIPLE,
    KEYWORD_SINGLE,
    KEYWORD_REENTRANT,
    KEYWORD_STATIC,
    KEYWORD_DYNAMIC,
    KEYWORD_GENERIC,
    KEYWORD_TIMEOUT,
    KEYWORD_RETRY,
    KEYWORD_NOEXCEPT,
    KEYWORD_ENUM,
    KEYWORD_STREAM,
    TERM_HASH, // #
    TERM_DOUBLE_SLASH, // //
    TERM_COMMENT_BEGIN, // /*
    TERM_COMMENT_END, // */
    TERM_QUOTE, // "
    TERM_LEFT_BRACE, // {
    TERM_RIGHT_BRACE, // }
    TERM_TEMP_LEFT, // <
    TERM_TEMP_RIGHT, // >
    TERM_COMMA, // ,
    TERM_LEFT_ROUND, // (
    TERM_RIGHT_ROUND, // )
    TERM_BLANK, // blank
    TERM_DOT, // .
    TERM_EQUAL, // =
    TERM_LEFT_BRACKET, // [
    TERM_RIGHT_BRACKET, // ]
    TERM_COLON, // :
};

class Tokenizer;

// Token
class Token {
    TokenType type_ {TokenType::NONE}; ///< Token type
    std::int32_t lineno_ {0}; ///< The line number where token located
    std::int32_t col_ {0}; ///< The column number where token located
    std::string text_; ///< The literal text of token
    std::string file_; ///< The file contains token
    Tokenizer* tokenizer_ {nullptr}; ///< Tokenizer

public:
    // ctor
    Token() {}
    // Copy ctor
    Token(const Token& rht) {
        type_ = rht.type_;
        lineno_ = rht.lineno_;
        col_ = rht.col_;
        text_ = rht.text_;
        file_ = rht.file_;
        tokenizer_ = rht.tokenizer_;
    }
    // ctor
    // @param type token type
    // @param lineno line number
    // @param col column number
    // @param text token text
    Token(TokenType type, std::int32_t lineno, std::int32_t col, const std::string& text, Tokenizer* tokenizer) {
        type_ = type;
        lineno_ = lineno;
        col_ = col;
        text_ = text;
        tokenizer_ = tokenizer;
    }
    // Assignment
    const Token& operator=(const Token& rht) {
        type_ = rht.type_;
        lineno_ = rht.lineno_;
        col_ = rht.col_;
        text_ = rht.text_;
        file_ = rht.file_;
        tokenizer_ = rht.tokenizer_;
        return *this;
    }
    // Returns token type
    inline TokenType getType() const {
        return type_;
    }
    // Update token type
    inline void setType(TokenType type) {
        type_ = type;
    }
    // Returns line number
    inline std::int32_t getLineno() const {
        return lineno_;
    }
    // Returns column number
    inline std::int32_t getCol() const {
        return col_;
    }
    // Returns text of token
    inline const std::string& getText() const {
        return text_;
    }

    inline std::string& mutableText() {
      return text_;
    }

    // Returns file name
    inline const std::string& getFile() const {
        return file_;
    }
    // Update file name
    inline void setFile(const std::string& file) {
        file_ = file;
    }
    // Validation
    operator bool() {
        return (type_ != TokenType::NONE);
    }
    // Returns null token
    static Token& nullToken() {
        static Token null;
        return null;
    }
    bool operator==(Token& rht) {
        return (lineno_ == rht.lineno_ && col_ == rht.col_ && text_ == rht.text_);
    }
    bool operator!=(Token& rht) {
        return !operator==(rht);
    }
    inline std::int32_t count() {
        return static_cast<std::int32_t>(text_.size());
    }
    // Returns tokenizer
    inline Tokenizer* getTokenizer() {
        return tokenizer_;
    }
    // Print token highlight
    // @param os output stream
    void printLine(std::ostream& os);
};

// Tokenizer
class Tokenizer {
    using TokenList = std::list<Token*>;
    TokenList tokenList_; ///< Token list
    TokenList tokenStack_; ///< Token stack
    Token curToken_; ///< Current token
    std::string error_; ///< Error string
    std::int32_t curLineno_ {1}; ///< Current line number
    std::int32_t curCol_ {1}; ///< Current column number
    std::string file_; ///< Current file name
    bool ignore_ {false}; ///< Ignore unrecognized token

public:
    // ctor
    Tokenizer(const std::string& file) : file_(file) {}
    // dtor
    ~Tokenizer();
    // Gets next token in tokenizer
    Token& getNext(std::istream& is);
    // push current token to stack
    void unget();
    // Returns current token
    inline Token& getCurToken() {
        if (curToken_) {
            return curToken_;
        } else {
            return Token::nullToken();
        }
    }
    // Returns error string
    inline const std::string& error() const {
        return error_;
    }
    // Ignore unrecognized token
    inline void ignore() {
        ignore_ = true;
    }
    // Raise error when meet unrecognized token
    inline void normal() {
        ignore_ = false;
    }
    // Print token highlight
    // @param token The token need to highlight
    // @param os output stream
    void printLine(Token& token, std::ostream& os);
    // Print token error
    // @param os output stream
    // @param lineno current line number
    // @param tabCount TAB count
    // @param scoreCount score count
    // @param spaceCount space count
    void printError(std::ostream& os, std::int32_t lineno, std::int32_t tabCount,
        std::int32_t scoreCount, std::int32_t spaceCount);

private:
    // Collect token forward
    // @param token token
    // @param list token list
    void collectTokenForward(Token& token, TokenList& list);
    // Collect token backward
    // @param token token
    // @param list token list
    // @param tabCount TAB count
    // @param scoreCount score count
    // @param spaceCount space count
    void collectTokenBackward(Token& token, TokenList& list, std::int32_t& tabCount,
        std::int32_t& scoreCount, std::int32_t& spaceCount);
    // Try to print error message
    // @param token token
    // @param list token list
    // @param lineno line number
    // @param tabCount TAB count
    // @param scoreCount score count
    // @param spaceCount space count
    // @param os output stream
    // @retval true success
    // @retval false failure
    bool tryPrintErrorMessage(Token& token, TokenList& list, std::int32_t& lineno,
        std::int32_t tabCount, std::int32_t scoreCount, std::int32_t spaceCount,
            std::ostream& os);
    // Step into next token
    Token& step(std::istream& is);
    // Try to get identifier token
    Token& getIdentifier(std::istream& is);
    // Try to get symbol token
    Token& getSymbol(std::istream& is);
    // Try to get blank token
    Token& getBlank(const char c);
    // Try to pop token from stack
    Token& popStack();
};

